package entity;

import com.alibaba.fastjson.JSONObject;
import game.Z5CGame;
import service.Message;
import utils.Common;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static game.Z5CGame.Camp.none;

/**
 * 并发安全游戏对战房间
 */
public class GameRoom {

    private static AtomicLong idCur;

    static {
        idCur = new AtomicLong(0L);
    }

    ///////////////////////////////////////////////////


    ///////////////////////////////////////////////////
    private long    id; //Auto increment

    private String  title;
    private int     ready; //准备好的人数
    private int     confiremd; //确认战果的人数
    private int     playerCount; //玩家人数
    private int     checkerboardSize;
    private String  passwordHash;

    private boolean visible;
    private boolean watchable;

    private Player  host;
    private Player  challenger;

    private HashSet<Player> visitors;

    private Z5CGame game;

    private ReentrantReadWriteLock lock;
    ///////////////////////////////////////////////////

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        GameRoom gameRoom = (GameRoom) o;
        return id == gameRoom.id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    ///////////////////////////////////////////////////

    public GameRoom(String title, Player host, String passwordHash) {

        this.id = idCur.addAndGet(1);

        this.title        = title;
        this.ready        = 0;
        this.confiremd    = 0;
        this.playerCount  = 0;
        this.passwordHash = passwordHash;
        this.visible      = false;
        this.watchable    = true;
        this.checkerboardSize = 15;
        host.enterGameRoomAsHost(this);
        this.challenger   = null;

        visitors = new HashSet<>();

        game = null;

        lock = new ReentrantReadWriteLock();
    }

    public final void broadcastToWatcher(String message) {
        lock.readLock().lock();
        try {
            for(Player visitor : visitors) {
                if(visitor != null) visitor.getSession().getAsyncRemote().sendText(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        lock.readLock().unlock();
    }

    /**
     * 广播消息
     * @param message 消息
     */
    public final void broadcast(String message) {
        lock.readLock().lock();
        try {
            if(host != null) host.getSession().getAsyncRemote().sendText(message);
            if(challenger != null) challenger.getSession().getAsyncRemote().sendText(message);
            broadcastToWatcher(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
        lock.readLock().unlock();
    }

    public void reset() {
        ready = 0;
        confiremd = 0;
    }

    public void restartGame() {
        if(game != null) {
            game.clear();
            reset();
        }
    }

    /**
     * 房主加入，由房主调用
     * @param host 房主
     */
    void hostJoin(Player host) {
        this.host = host;
        ++playerCount;
    }

    /**
     * 加入对战，应由挑战者调用
     * @param p 玩家
     * @return 加入成功返回true，否则返回false
     */
    boolean challengerJoin(Player p) {
        boolean joined = false;
        lock.writeLock().lock();
        if(challenger == null) {
            //向房间现有玩家广播挑战者的信息
            broadcast(Message.challengerJoin(p));
            challenger = p;
            ++playerCount;
            //向挑战者发送房间信息
            challenger.getSession().getAsyncRemote().sendText(Message.getRoomDetail(this));
            joined = true;
        }
        lock.writeLock().unlock();
        return joined;
    }

    /**
     * 挑战者退出，应由挑战者调用
     */
    void challengerLeave() {
        JSONObject json = new JSONObject();
        json.put("type", "challenger_leave");

        lock.writeLock().lock();

        if(playerConfirm() == 2) {
            json.put("confirmed", true);
        }

        if(challenger.getCamp() != null && challenger.getCamp() != none) {
            json.put(challenger.getCamp().name() + "Img", "none");
            if(getReady() == 2) {
                json.put("runOfBattle", true);
            }
        }

        challenger = null;
        host.setReady(false);
        broadcast(json.toJSONString());

        --playerCount;
        reset();
        lock.writeLock().unlock();
    }

    /**
     * 观众加入，由观众调用
     * @param v 观众
     */
    void visitorJoin(Player v) {
        lock.writeLock().lock();
        visitors.add(v);
        if(getReady() == 2) sendCheckerboard(v);
        lock.writeLock().unlock();
    }

    private void sendCheckerboard(Player visitor) {
        JSONObject resp = new JSONObject();
        resp.put("type", "getCheckerboard");
        resp.put("siz", game.getSize());
        resp.put("data", game.getHistory());
        visitor.getSession().getAsyncRemote().sendText(resp.toJSONString());
        //System.out.println("GameRoom(obj).sendCheckerboard: Checkerboard sent: " + resp.toJSONString());
    }

    /**
     * 观众离开，由观众调用
     * @param v 观众
     */
    void visitorLeave(Player v) {
        lock.writeLock().lock();
        visitors.remove(v);
        lock.writeLock().unlock();
    }

    /**
     * 清出观众
     */
    void removeVistors() {
        for(Player visitor : visitors) {
            visitor.leave();
        }
        visitors.clear();
    }

    /**
     * 房主退出，应由房主调用
     * @return 如果房间清空，返回true，否则返回false
     */
    boolean hostLeave() {

        boolean clean = false;

        JSONObject json = new JSONObject();
        json.put("type", "swap_host");

        lock.writeLock().lock();

        if(playerConfirm() == 2) {
            json.put("confirmed", true);
        }

        if(--playerCount > 0) {

            //在轮换房主后，通知新的房主上任
            if(challenger != null) {
                if(host.getCamp() != null && host.getCamp() != none) {
                    json.put(host.getCamp().name() + "Img", "none");
                    if(getReady() == 2) {
                        //host.setReady(false);
                        json.put("runOfBattle", true);
                    }
                }

                host = challenger; //挑战者晋升为房主
                challenger = null;
                host.setReady(false);
                try {
                    host.getSession().getAsyncRemote().sendText(json.toJSONString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            reset();
            broadcastToWatcher(Message.HOST_LEAVE);
        } else { //房间关闭
            host = null;
            removeVistors();
            clean = true;
        }

        lock.writeLock().unlock();
        return clean;
    }

    private final Z5CGame.Result go(JSONObject json, Z5CGame.Camp camp) {
        lock.readLock().lock();
        Z5CGame.Result result = game.go((Integer)json.get("row"), (Integer)json.get("col"), camp);
        lock.readLock().unlock();

        switch(result.getResult()) {
            case accept:
                json.put("type", "go");
                String response = json.toJSONString();
                broadcast(response);
                break;
            case reject: break;
            case blackWin: {
                json.put("type", "blackWin");
                json.put("win", result.getWinBy());
                broadcast(json.toJSONString());
                lock.writeLock().lock();
                ready = 0;
                lock.writeLock().unlock();
                break;
            }
            case whiteWin: {
                json.put("type", "whiteWin");
                json.put("win", result.getWinBy());
                broadcast(json.toJSONString());
                lock.writeLock().lock();
                ready = 0;
                lock.writeLock().unlock();
                break;
            }
            case draw:
                broadcast(Message.DRAWMESSAGE);
                break;
        }

        return result;
    }

    private final Z5CGame.Result rollback(JSONObject request) {
        /* case "rollback":
         * 	Checkerboard.rollback(response.rbk[0], response.rbk[1], response.rbk[2], response.rbk[3]);
         * 	break;
         */
        Z5CGame.Camp camp = Z5CGame.getCamp((String) request.get("cmp"));

        lock.readLock().lock();
        int [] rollbackResult = game.rollback(camp);
        lock.readLock().unlock();
        //System.out.println("doRollback: [" + rollbackResult[0] + ',' + rollbackResult[1] + ',' + rollbackResult[2] + ',' + rollbackResult[3] + ']');
        if(rollbackResult[0] != -1) {
            request.put("type", "rollback");
            request.put("rbk", rollbackResult);
            broadcast(request.toJSONString());
            return Z5CGame.ACCEPT;
        }
        return Z5CGame.REJECT;
    }

    public final int checkPlayerReady() {
        lock.writeLock().lock();
        ready = 0;
        if(host != null && host.isReady()) ++ready;
        if(challenger != null && challenger.isReady()) ++ready;
        lock.writeLock().unlock();
        return ready;
    }

    public final Z5CGame.Result play(String msg, Z5CGame.Camp camp) {
        if(game == null || Common.isStringEmpty(msg)) return Z5CGame.REJECT;

        JSONObject json = JSONObject.parseObject(msg);

        switch((String)json.get("type")) {
            case "requestGo":
                if(getReady() == 2) return go(json, camp);
            case "requestRollback":
                return rollback(json);
        }
        return Z5CGame.REJECT;
    }

    public void configRoom(JSONObject message) {
        String  title        = message.getString("roomTitle");
        String  passwordHash = message.getString("roomPass");
        Boolean watchable    = message.getBoolean("roomWatch");
        Integer size         = message.getInteger("cbSize");

        lock.writeLock().lock();
        if (title != null && !Common.isStringEmpty(title)) {
            setTitle(title);
        } else return;

        if (size != null) {
            setCheckerboardSize(size);
        } else return;

        if (passwordHash != null) {
            setPasswordHash(passwordHash);
        } else setPasswordHash(null);
        if (watchable != null) setWatchable(watchable);

        game = new Z5CGame(getCheckerboardSize());
        setVisible(true);

        message.put("type", "configRoomSucceed");
        String jsonMsg = message.toJSONString();

        if (getHost() != null) {
            getHost().getSession().getAsyncRemote().sendText(jsonMsg);
        }

        if (getChallenger() != null) {
            getChallenger().getSession().getAsyncRemote().sendText(jsonMsg);
        }
        lock.writeLock().unlock();
    }

    public void configPlayerCheckerboard() {
        game.clear();
        JSONObject response = new JSONObject();
        response.put("type", "configCheckerboard");
        response.put("siz", getCheckerboardSize());

        Z5CGame.Camp hostCamp = null;
        Z5CGame.Camp chlgCamp = null;

        lock.readLock().lock();
        hostCamp = host.getCamp();
        chlgCamp = challenger.getCamp();
        lock.readLock().unlock();

        //令前端页面重设棋盘
        response.put("cmp", hostCamp);
        host.getSession().getAsyncRemote().sendText(response.toJSONString());
        response.put("cmp", chlgCamp);
        challenger.getSession().getAsyncRemote().sendText(response.toJSONString());
        response.put("cmp", none);
        broadcastToWatcher(response.toJSONString());
    }

    public final long getId() {
        return id;
    }

    public final int getReady() { return ready; }

    public final Player getHost() {
        return host;
    }

    public final String getTitle() {
        return title;
    }

    public final boolean isVisible() { return visible; }

    public final boolean isWatchable() { return watchable; }

    public final int getPlayerCount() {
        return playerCount;
    }

    public final Player getChallenger() {
        return challenger;
    }

    public final Set<Player> getVisitors() {
        return visitors;
    }

    public final String getPasswordHash() {
        return passwordHash;
    }

    public final void setTitle(String title) { this.title = title; }

    public final void setVisible(boolean visible) { this.visible = visible; }

    public final void setWatchable(boolean watchable) { this.watchable = watchable; }

    public final void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }

    public final int getCheckerboardSize() { return checkerboardSize; }

    public final void setCheckerboardSize(int checkerboardSize) { this.checkerboardSize = checkerboardSize; }

    //public final int getConfiremd() { return confiremd; }

    public final int playerConfirm() {
        int ret;
        if((ret = ++confiremd) >= 2) {
            cleanConfirm();
            return 2;
        }
        return ret;
    }

    public final void cleanConfirm() {
        this.confiremd = 0;
    }
}
//TODO 得分计算
//TODO 房间设定面版隐藏
//TODO 观众系统